package com.smaato;

import java.io.UnsupportedEncodingException;
import java.util.Vector;

import net.rim.blackberry.api.phone.Phone;
import net.rim.device.api.crypto.DigestOutputStream;
import net.rim.device.api.crypto.MD5Digest;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Branding;
import net.rim.device.api.system.Device;
import net.rim.device.api.system.DeviceInfo;
import net.rim.device.api.system.Display;
import net.rim.device.api.system.EncodedImage;
import net.rim.device.api.system.GPRSInfo;
import net.rim.device.api.ui.Font;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.UiApplication;
import com.smaato.SmaatoAPI.*;
import com.smaato.comms.CommsListener;
import com.smaato.comms.CommsManager;
import com.smaato.comms.URLUTF8Encoder;
import com.smaato.gps.GPSEngine;
import com.smaato.graphics.PNGEncoder;
import com.smaato.logging.Log;
import com.smaato.xml.SmaatoXMLHandler;
import com.smaato.xml.SmaatoXMLResponse;

class SmaatoAPIImpl implements CommsListener
{
	/* 
	 * Required Parameters
	 */
	private final String iAdSpace;
	private final String iPublisherId;	
	private String 	  iAdFormat;
	private String 	  iResponseType;
	private int 	  iHeight = -1;
	private int 	  iWidth = -1;
	private int		  iTextAdBgColor = 0x000000;
	private int 	  iTextAdFgColor = 0xFFFFFF;
		
	/* 
	 * Optional Parameters
	 * 
	 * User information.
	 */
	private String iAge;
	private String iGender;
	private String iCity;
	private String iCountry;
	private String iCountryCode;
	private String iState;
	private String iZip;
	private String iEthnicity;
	private String iIncome;
	private String iEducation;
	private String iDOB;
	private String iGenderSought;
	private String iMaritalStatus;
	
	/* 
	 * GPS Parameters
	 */
	private boolean iGetGPS;
	private String	iAreaCode;
	private String	iGPSCoOrds;
	private GPSEngine iGPSEngine;
	
	/*
	 * Search Parameters
	 */
	private String iKeywordString;
	private String iQueryString;
	
	/*
	 * Device parameters.
	 */
	private String iClientVersion;
	private String iDeviceMake;
	private String iDeviceModel;
	private String iOwnID;
	private String iMSISDNHash;
	
	/*
	 * Session parameters.
	 */
	private String iSessionID;
		
	/*
	 * Additional connection parameter
	 */
	private String iConnectionParameter;
	
	/*
	 * Constant values
	 */
	private static final String ClientVersion = "sdkblackberry-09";
	private static final String DeviceMake = "blackberry";
	
	private String baseUrl = "http://soma.smaato.com/oapi/reqAd.jsp";
	
	private static final int 	DOBLength = 8;
	private static final String comma = "%2C";
	private static final String responseType = "XML";
	
	private static final int DefaultWidth = Display.getWidth();
	private static final int DefaultHeight = 28;
	private static final int DefaultPadding = 20;
	
	/*
	 * Download state values
	 */
	private static final int IdleState = 0;
    private static final int DownloadXMLState = 1;
    private static final int DownloadAdState = 2;
    private int iDownloadState = IdleState;
      
    /*
     * Local variables
     */
    private SmaatoXMLResponse iXMLResponse = null;
    private SmaatoAdImpl iSmaatoAd = null;  
    private SmaatoAPICallback iListener;
    

	/*
	 * Error Strings
	 */
	private static final String ErrNoValidAdsReturnedString = "No valid ads returned from the server";
	private static final String ErrNoContentInServerResponseString = "No Content in server response";
	private static final String ErrParserErrorString = "Error parsing server response";
	private static final String ErrDownloadAdFailedString = "Error downloading ad from server";
	private static final String ErrTextAdSizeString = "Error creating text ad, size too small for text";
	private static final String ErrAdCreationString = "Error creating ad image";
	
	public SmaatoAPIImpl(String adSpace, String publisher, String adFormat, boolean getGPS)
	{
		if(adSpace == null || adSpace.length() == 0 || publisher == null || publisher.length() == 0)
		{
			throw new IllegalArgumentException("AdSpace or PublisherId not set");
		}
		
		if(adSpace.equals("0") || publisher.equals("0"))
		{
			throw new IllegalArgumentException("Adspace or PublisherId invalid");
		}
		
		iAdSpace = adSpace;
		iPublisherId = publisher;
		setAdFormat(adFormat);		
		iGetGPS = getGPS;
		
		iResponseType = responseType;
		iClientVersion = ClientVersion;
		iDeviceMake = DeviceMake;
		iDeviceModel = DeviceInfo.getDeviceName();
		iOwnID = MD5Hash(Integer.toString(DeviceInfo.getDeviceId()));
		iMSISDNHash = MD5Hash(Phone.getDevicePhoneNumber(false));
		iSessionID = MD5Hash(Integer.toString(DeviceInfo.getDeviceId()) + System.currentTimeMillis());
		iAreaCode = Integer.toString(GPRSInfo.getCellInfo().getLAC());

		if(iGetGPS)
		{
			iGPSEngine = new GPSEngine();
		}
	}
	
	public void getAd(SmaatoAPICallback aListener) throws IllegalStateException
	{
		if(aListener == null)
		{
			throw new IllegalArgumentException("Callback not set");
		}
		
		if(iDownloadState != IdleState)
		{
			//throw new IllegalStateException("SmaatoAPI is busy processing transaction");
		}
		else
		{
			iXMLResponse = null;
			iSmaatoAd = null;
			iListener = aListener;
			String url = buildURL();
			CommsManager.getInstance().getDataAndCallback(url, this);
			iDownloadState = DownloadXMLState;
		}
	}
	
	public SmaatoAd showAd()
	{
		if(iSmaatoAd == null && iDownloadState != IdleState)
		{
			throw new IllegalStateException("SmaatoAPI is busy processing transaction");
		}
		
		if(iXMLResponse.getAd(0).getBeaconCount() > 0)
		{
			Vector beacons = iXMLResponse.getAd(0).getBeacons();
			for(int i=0; i < beacons.size(); i++)
			{
				String beaconUrl = ((SmaatoXMLResponse.BeaconInfo)beacons.elementAt(i)).getBeaconUrl();
				if(iConnectionParameter != null)
				{
					beaconUrl += iConnectionParameter;
				}
				CommsManager.getInstance().getData(beaconUrl);
			}
		}
		
		return iSmaatoAd;
	}
	
	public void setConnectionParameter(String aConnectionParam)
	{
		if(aConnectionParam != null)
		{
			if(aConnectionParam.length() == 0)
			{
				throw new IllegalArgumentException("ConnectionParam not set");
			}
		}
		
		iConnectionParameter = aConnectionParam;
	}

	public void setAdFormat(String adFormat)
	{
		if(adFormat != null)
		{
			if(adFormat == null || adFormat.length() == 0)
			{
				throw new IllegalArgumentException("AdFormat not set");
			}
			
			if(!adFormat.equals(SmaatoAPI.AdFormat.Image) &&
			   !adFormat.equals(SmaatoAPI.AdFormat.Text) &&
			   !adFormat.equals(SmaatoAPI.AdFormat.All))
			{
				throw new IllegalArgumentException("AdFormat is not a valid format");
			}
		}
		
		iAdFormat = adFormat;
	}
	
	public void setHeight(int height)
	{
		if(height < 20 || height > 53)
		{
			throw new IllegalArgumentException("Invalid height value");
		}
		
		iHeight = height;
	}
	
	public void setWidth(int width)
	{
		if(width < 120 || width > 320)
		{
			throw new IllegalArgumentException("Invalid height value");
		}
		
		iWidth = width;
	}
	
	public void setTextAdBgColor(int color)
	{
		if(color < 0 || color > 0xFFFFFF)
		{
			throw new IllegalArgumentException("Invalid color value");
		}
		iTextAdBgColor = color;
	}
	
	public void setTextAdFgColor(int color)
	{
		if(color < 0 || color > 0xFFFFFF)
		{
			throw new IllegalArgumentException("Invalid color value");
		}
		iTextAdFgColor = color;
	}
	
	public void setAge(int age)
	{
		if(age < 0)
		{
			throw new IllegalArgumentException("Invalid height value");
		}
		
		iAge = Integer.toString(age);
	}
	
	public void setGender(String gender)
	{
		if(gender != null)
		{
			if(gender.length() == 0)
			{
				throw new IllegalArgumentException("Gender not set");
			}
			
			if(!gender.equals(Gender.Male) && !gender.equals(Gender.Female))
			{
				throw new IllegalArgumentException("Gender value is not a valid gender");
			}
		}
		
		iGender = gender;
	}
	
	public void setCity(String city)
	{
		if(city != null)
		{
			if(city.length() == 0)
			{
				throw new IllegalArgumentException("City not set");
			}
		}
		
		iCity = city;
	}
	
	public void setCountry(String country)
	{
		if(country != null)
		{
			if(country.length() == 0)
			{
				throw new IllegalArgumentException("Country not set");
			}
		}
		
		iCountry = country;
	}
	
	public void setCountryCode(String countryCode)
	{
		if(countryCode != null)
		{
			if(countryCode.length() == 0)
			{
				throw new IllegalArgumentException("CountryCode not set");
			}
		}
		
		iCountryCode = countryCode;
	}
	
	public void setState(String state)
	{
		if(state != null)
		{
			if(state.length() == 0)
			{
				throw new IllegalArgumentException("State not set");
			}
		}
		
		iState = state;
	}
	
	public void setZIP(String zip)
	{
		if(zip != null)
		{
			if(zip.length() == 0)
			{
				throw new IllegalArgumentException("Zip not set");
			}
		}
		
		iZip = zip;
	}
	
	public void setEthnicity(String ethnicity)
	{
		if(ethnicity != null)
		{
			if(ethnicity.length() == 0)
			{
				throw new IllegalArgumentException("Ethnicity not set");
			}
			
			if(!ethnicity.equals(Ethnicity.White) && 
			   !ethnicity.equals(Ethnicity.MiddleEastern) &&
			   !ethnicity.equals(Ethnicity.Black) &&
			   !ethnicity.equals(Ethnicity.Latino) &&
			   !ethnicity.equals(Ethnicity.SouthAsian) &&
			   !ethnicity.equals(Ethnicity.Oriental) &&
			   !ethnicity.equals(Ethnicity.Other))
			{
				throw new IllegalArgumentException("Ethnicity value is not a valid ethnicity");
			}
		}
		
		iEthnicity = ethnicity;
	}
	
	public void setIncome(String income)
	{
		if(income != null)
		{
			if(income.length() == 0)
			{
				throw new IllegalArgumentException("Income not set");
			}
			
			if(!income.equals(Income.LessThan15k) && 
			   !income.equals(Income.From15to24k) &&
			   !income.equals(Income.From25kto39k) &&
			   !income.equals(Income.From40kto59k) &&
			   !income.equals(Income.From60kto74k) &&
			   !income.equals(Income.From75kto99k) &&
			   !income.equals(Income.MoreThan100k))
			{
				throw new IllegalArgumentException("Income value is not a valid income");
			}
		}
		
		iIncome = income;
	}
	
	public void setEducation(String education)
	{
		if(education != null)
		{
			if(education.length() == 0)
			{
				throw new IllegalArgumentException("Education not set");
			}
			
			if(!education.equals(Education.LessThanSecondary) && 
			   !education.equals(Education.Secondary) &&
			   !education.equals(Education.University) &&
			   !education.equals(Education.Advanced))
			{
				throw new IllegalArgumentException("Education value is not a valid education");
			}
		}

		iEducation = education;
	}
	
	public void setDateOfBirth(String dob)
	{
		if(dob != null)
		{
			if(dob.length() == 0)
			{
				throw new IllegalArgumentException("Date of Birth not set");
			}
			
			if(dob.length() != DOBLength)
			{
				throw new IllegalArgumentException("Date of Birth value is not valid, must be of in the format YYYYMMDD");
			}
		}
		
		iDOB = dob;
	}
	
	public void setGenderSought(String genderSought)
	{
		if(genderSought != null)
		{
			if(genderSought.length() == 0)
			{
				throw new IllegalArgumentException("GenderSought not set");
			}
			
			if(!genderSought.equals(GenderSought.Male) && 
			   !genderSought.equals(GenderSought.Female) &&
			   !genderSought.equals(GenderSought.Both))
			{
				throw new IllegalArgumentException("Gender sought value is not a valid gender sought");
			}
		}
		
		iGenderSought = genderSought;
	}
	
	public void setMaritalStatus(String maritalStatus)
	{
		if(maritalStatus != null)
		{
			if(maritalStatus.length() == 0)
			{
				throw new IllegalArgumentException("MaritalStatus not set");
			}
			
			if(!maritalStatus.equals(MaritalStatus.Single) && 
			   !maritalStatus.equals(MaritalStatus.Divorced) &&
			   !maritalStatus.equals(MaritalStatus.Married))
			{
				throw new IllegalArgumentException("Marital status value is not a valid marital status");
			}
		}
		
		iMaritalStatus = maritalStatus;
	}
	
	public void setAreaCode(String areaCode)
	{
		if(areaCode != null)
		{
			if(areaCode.length() == 0)
			{
				throw new IllegalArgumentException("AreaCode not set");
			}
		}
		
		iAreaCode = areaCode;
	}
	
	public void setGPSCoords(double latitude, double longitude)
	{		
		iGPSCoOrds = Double.toString(latitude) + comma + Double.toString(longitude);
	}
	
	public void setKeywordString(String keywordString)
	{
		if(keywordString != null)
		{
			if(keywordString.length() == 0)
			{
				throw new IllegalArgumentException("KeywordString not set");
			}
		}
		
		iKeywordString = keywordString;
	}
	
	public void setQueryString(String queryString)
	{
		if(queryString != null)
		{
			if(queryString.length() == 0)
			{
				throw new IllegalArgumentException("QueryString not set");
			}
		}
		
		iQueryString = queryString;
	}
	
    private String MD5Hash(String text)
    {
    	if(text == null || text.length() == 0 || text.equals(""))
    	{
    		// invalid input
    		return null;
    	}
    	
    	byte[] plainText = text.getBytes();
    	MD5Digest digest = new  MD5Digest();
        DigestOutputStream digestStream = new DigestOutputStream(digest, null);
               
        try
        {
        	digestStream.write(plainText);
        }
        catch(Exception e)
        {
        	// Something went wrong
        	return null;
        }
        
        byte[] hashedData = new byte[digest.getDigestLength()];
        digest.getDigest(hashedData, 0);
        
        String res = "";
        for(int i=0; i < hashedData.length; i++)
        {
        	String hashed = Integer.toString( ( hashedData[i] & 0xff ) + 0x100, 16).substring( 1 );
        	res += hashed;
        }
		
        return res;
    }
    
    private String buildURL()
	{
		String url = baseUrl
				+ "?response=" + iResponseType
				+ "&javascriptenabled=true"
				+ "&adspace=" + iAdSpace 
				+ "&pub=" + iPublisherId 			
				+ "&format=" + iAdFormat;
		
		if(iHeight != -1 && iWidth != -1)
		{
			url += "&height=" + iHeight + "&width=" + iWidth;
		}
		
		if(iOwnID != null)
		{
			url += "&ownid=" + iOwnID;
		}
				
		if(iMSISDNHash != null)
		{
			url += "&msisdn=" + iMSISDNHash;
		}
		
		if(iSessionID != null)
		{
			url += "&session=" + iSessionID;
		}
		
		if(iClientVersion != null)
		{
			url += "&client=" + iClientVersion;
		}

		if(iDeviceMake != null)
		{
			url += "&devicemake=" + iDeviceMake;
		}
		
		if(iDeviceModel != null)
		{
			url += "&devicemodel=" + iDeviceModel;
		}
		
		if(iAge != null)
		{
			url += "&age=" + iAge;
		}
		
		if (iDOB != null) 
		{
			url += "&dateofbirth=" + iDOB;
		}
		
		if(iGender != null)
		{
			url += "&gender=" + iGender;
		}
		
		if(iEthnicity != null)
		{
			url += "&ethnicity=" + iEthnicity;
		}
		
		if(iIncome != null)
		{
			url += "&income=" + iIncome;
		}
		
		if(iEducation != null)
		{
			url += "&education=" + iEducation;
		}
		
		if(iMaritalStatus != null)
		{
			url += "&maritalstatus=" + iMaritalStatus;
		}
		
		if(iGenderSought != null)
		{
			url += "&gendersought=" + iGenderSought;
		}

		if(iCity != null) 
		{
			iCity = URLUTF8Encoder.encode(iCity);
			url += "&city=" + iCity;
		}
		
		if(iCountry != null) 
		{
			iCountry = URLUTF8Encoder.encode(iCountry);
			url += "&country=" + iCountry;
		}
		
		if(iCountryCode != null)
		{
			url += "&countrycode=" + iCountryCode;
		}
		
		if(iState != null) 
		{
			iState = URLUTF8Encoder.encode(iState);
			url += "&state=" + iState;
		}
		
		if(iZip != null) 
		{
			iZip = URLUTF8Encoder.encode(iZip);
			url += "&zip=" + iZip;
		}
		
		if(iAreaCode != null)
		{
			iAreaCode = URLUTF8Encoder.encode(iAreaCode);
			url += "&areacode=" + iAreaCode;
		}
		
		if(iGPSEngine != null && iGPSEngine.isGPSDataReady())
		{
			Log.debug("GPS Coords received - " + iGPSEngine.getGPSData().getLatitude() + " : " + iGPSEngine.getGPSData().getLongitude());
			setGPSCoords(iGPSEngine.getGPSData().getLatitude(), iGPSEngine.getGPSData().getLongitude());
		}
		
		if(iGPSCoOrds != null)
		{
			url += "&gps=" + iGPSCoOrds;
		}
		
		if(iKeywordString != null) 
		{
			iKeywordString = URLUTF8Encoder.encode(iKeywordString);
			url += "&kws=" + iKeywordString;
		}
		
		if(iQueryString != null) 
		{
			iQueryString = URLUTF8Encoder.encode(iQueryString);
			url += "&qs=" + iQueryString;
		}
		
		if(iConnectionParameter != null)
		{
			url += iConnectionParameter;
		}
		
		Log.debug("URL: " + url);
				
		return url;
	}

	public void notifyCommsSuccess(byte[] data)
	{
		if(data == null)
		{
			callErrorCallback(SmaatoAPI.ErrNoContentInServerResponse, ErrNoContentInServerResponseString);
			iDownloadState = IdleState;
			return;
		}
		
		if(iDownloadState == DownloadXMLState)
		{
			SmaatoXMLHandler handler = new SmaatoXMLHandler();
			iXMLResponse = handler.parseXML(data);
			if(iXMLResponse == null)
			{
				callErrorCallback(SmaatoAPI.ErrParserError, ErrParserErrorString);	
				iDownloadState = IdleState;
				return;
			}
			
			if(!iXMLResponse.getSuccess().equalsIgnoreCase("success"))
			{
				int errorCode = Integer.parseInt(iXMLResponse.getErrorCode());
				String errorString = iXMLResponse.getErrorString();
				callErrorCallback(errorCode, errorString);				
				iDownloadState = IdleState;
				return;
			}
			
			if(iXMLResponse.getAdsCount() <= 0)
			{
				callErrorCallback(SmaatoAPI.ErrNoValidAdsReturned, ErrNoValidAdsReturnedString);
				iDownloadState = IdleState;
				return;
			}
			
			if(iXMLResponse.getAd(0).getAdType().equalsIgnoreCase("TXT"))  
			{
				// Success, text ad
				EncodedImage image = createTextAd(iXMLResponse.getAd(0).getAdText());
				if(image == null)
				{
					callErrorCallback(SmaatoAPI.ErrAdCreation, ErrAdCreationString);
				}
				else
				{
					String targetUrl = iXMLResponse.getAd(0).getTarget();
					if(iConnectionParameter != null)
					{
						targetUrl += iConnectionParameter;
					}
					iSmaatoAd = new SmaatoAdImpl(image, targetUrl);
					callSuccessCallback();
				}
				
				iDownloadState = IdleState;
			}
			else
			{
				// Success, download the image
				String imageUrl = iXMLResponse.getAd(0).getLink();
				if(iConnectionParameter != null)
				{
					imageUrl += iConnectionParameter;
				}
				CommsManager.getInstance().getDataAndCallback(imageUrl, this);
				iDownloadState = DownloadAdState;
			}
		}
		else if(iDownloadState == DownloadAdState)
		{
			if(data != null)
			{
				// Success, create smaatoAd
				Log.debug("Action Target: " + iXMLResponse.getAd(0).getTarget());
				iSmaatoAd = new SmaatoAdImpl(data, iXMLResponse.getAd(0).getTarget());
				callSuccessCallback();
			}
			else
			{
				callErrorCallback(SmaatoAPI.ErrDownloadAdFailed, ErrDownloadAdFailedString);
			}
			
			iDownloadState = IdleState;
		}
	}
		
	public void notifyCommsError(final int error)
	{
		callErrorCallback(error, "Communication with the server failed");
		iDownloadState = IdleState;
	}
	
	private void callErrorCallback(final int errorCode, final String errorString)
	{
		UiApplication.getUiApplication().invokeLater(new Runnable()
		{
			public void run()
			{
				iListener.getAdNotifyError(errorCode, errorString);
			}
		});
	}
	
	private void callSuccessCallback()
	{
		UiApplication.getUiApplication().invokeLater(new Runnable()
		{
			public void run()
			{
				iListener.getAdNotifySuccess();
			}
		});
	}
	
	private EncodedImage createTextAd(String text)
	{
		int _localWidth, _localHeight;
		
		if(iHeight == -1 || iWidth == -1)
		{
			// Size not set, use default
			_localWidth = DefaultWidth;
			_localHeight = DefaultHeight;
		}
		else
		{
			_localWidth = iWidth;
			_localHeight = iHeight;
		}
		
		Bitmap image = new Bitmap(_localWidth, _localHeight);
		Graphics g = new Graphics(image);
		
		g.setBackgroundColor(iTextAdBgColor);
		g.clear();
		
		Font font = getFontForTextAndSize(text, _localWidth, _localHeight, g.getFont());
		if(font == null)
		{
			// Couldn't create font to draw the text into this image size
			callErrorCallback(SmaatoAPI.ErrTextAdSize, ErrTextAdSizeString);
			return null;
		}
		
		g.setFont(font);
		g.setColor(iTextAdFgColor);		
		g.drawText(text, 0, _localHeight / 2, (Graphics.VCENTER | Graphics.HCENTER), _localWidth);
		
		EncodedImage eImage = null;
		try
		{
			PNGEncoder encoder = new PNGEncoder(image, true);
			byte[] imageBytes = encoder.encode(true);
			eImage = EncodedImage.createEncodedImage(imageBytes, 0, imageBytes.length);
		}
		catch(Exception e)
		{
			// Couldn't convert image, null will be returned
		}
		
		return eImage;
	}
	
	private Font getFontForTextAndSize(String text, int width, int height, Font currentFont)
	{
		Font tempFont = null;
		int fontHeight = currentFont.getHeight();
		
		int advance = currentFont.getAdvance(text);
		while(advance > (width - DefaultPadding) && fontHeight > 0)
		{
			fontHeight--;
			tempFont = currentFont.derive(currentFont.getStyle(), fontHeight);
			int tempAdvance = tempFont.getAdvance(text);
			
			if(fontHeight == 0)
			{
				// We ran out of height and still not small enough
				return null;
			}
			else
			{
				advance = tempAdvance;
				currentFont = tempFont;
			}
		}
		
		if(tempFont == null)
		{
			// currentFont is good
			return currentFont;
		}
		else
		{
			return tempFont;
		}
	}
}
